package command

import (
	"fmt"
	"io/fs"
	"log"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"gorm.io/gorm"
	"gorm.io/gorm/schema"

	"gitee.com/csingo/cHelper"
	"gitee.com/csingo/cProto"

	"gitee.com/csingo/csingo/resource/template"
)

type AppCommand struct{}

func (i *AppCommand) Desc() *CommandDesc {
	return &CommandDesc{
		Name: "app",
		Methods: []*CommandMethod{
			{
				Name: "create",
				Desc: "创建应用",
				Params: []*CommandParams{
					{Name: "name", Desc: "应用名称"},
				},
			},
			{
				Name: "controller",
				Desc: "创建控制器",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "控制器名称"},
				},
			},
			{
				Name: "service",
				Desc: "创建应用服务",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "grpc", Desc: "是否生成grpc文件 [true | false]，默认 false 不生成", Optional: true},
					{Name: "protoPath", Desc: "proto文件目录绝对路径，默认当前项目proto目录", Optional: true},
				},
			},
			{
				Name: "domain",
				Desc: "创建领域服务",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "领域服务名称"},
				},
			},
			{
				Name: "model",
				Desc: "创建模型",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "模型名称"},
					{Name: "table", Desc: "数据表名"},
					{Name: "dsn", Desc: "数据库连接, eg: mysql://username:password@ip:port/db"},
					{Name: "driver", Desc: "模型使用的数据库配置名，默认 default", Optional: true},
				},
			},
			{
				Name: "command",
				Desc: "创建指令",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "命令名称"},
				},
			},
			{
				Name: "event",
				Desc: "创建事件",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "事件名称"},
				},
			},
			{
				Name: "middleware",
				Desc: "创建中间件",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "中间件名称"},
				},
			},
			{
				Name: "validator",
				Desc: "创建验证器",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称"},
					{Name: "name", Desc: "验证器名称"},
				},
			},
			{
				Name: "sdk",
				Desc: "创建SDK",
				Params: []*CommandParams{
					{Name: "app", Desc: "应用名称 ( proto 文件所在项目 ) "},
					{Name: "pkg", Desc: "目录名称", Optional: true},
					{Name: "grpc", Desc: "是否生成grpc文件 [true | false]，默认 false 不生成", Optional: true},
					{Name: "protoPath", Desc: "proto文件目录绝对路径，默认当前项目proto目录", Optional: true},
				},
			},
		},
	}
}

func (i *AppCommand) Create(name string) {
	ParamCheck("name", name)
	currentDir := GetCurrentDir()
	files := template.AssetNames()
	var err error
	for _, file := range files {
		if !strings.HasPrefix(file, "template/app/demo/") || !strings.HasSuffix(file, "/.gitkeep.tpl") {
			continue
		}

		targetFilePath := strings.TrimPrefix(file, "template/app/demo")
		targetFilePath = strings.TrimSuffix(targetFilePath, ".tpl")
		targetFilePath = filepath.Clean(fmt.Sprintf("%s/app/%s%s", currentDir, name, targetFilePath))

		targetFileDir := filepath.Dir(targetFilePath)
		err = os.MkdirAll(targetFileDir, 0755)
		HandleError(err)

		content, err := template.Asset(file)
		HandleError(err)

		err = os.WriteFile(targetFilePath, content, 0755)
		HandleError(err)
	}

	protoPath := filepath.Clean(fmt.Sprintf("%s/proto/%s", currentDir, name))
	if !cHelper.IsExistsPath(protoPath) {
		os.MkdirAll(protoPath, 0755)
	}

	BeautifyImports()
}

func (i *AppCommand) Command(app, name string) {
	i.createFileFromDemo(app, name, "command", nil)
	i.autoloadInject(app, name, "command")
	BeautifyImports()
}

func (i *AppCommand) Controller(app, name string) {
	i.createFileFromDemo(app, name, "controller", nil)
	i.autoloadInject(app, name, "controller")
	BeautifyImports()
}

func (i *AppCommand) Middleware(app, name string) {
	i.createFileFromDemo(app, name, "middleware", nil)
	i.autoloadInject(app, name, "middleware")
	BeautifyImports()
}

func (i *AppCommand) Validator(app, name string) {
	i.createFileFromDemo(app, name, "validator", nil)
	i.autoloadInject(app, name, "validator")
	BeautifyImports()
}

func (i *AppCommand) Repository(app, name string) {
	i.createFileFromDemo(app, name, "repository", nil)
	BeautifyImports()
}

func (i *AppCommand) Model(app, name, table, dsn, driver string) {
	ParamCheck("app", app)
	ParamCheck("name", name)
	ParamCheck("dsn", dsn)
	if driver == "" {
		driver = "default"
	}

	conn, dbname := ConnectDSN(dsn)

	db, err := gorm.Open(conn, &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 使用单数表名
		},
	})
	HandleError(err)

	type Columns struct {
		ColumnName string `gorm:"column:COLUMN_NAME"`
		ColumnType string `gorm:"column:COLUMN_TYPE"`
		DataType   string `gorm:"column:DATA_TYPE"`
	}

	var columns = []*Columns{}
	err = db.Table("INFORMATION_SCHEMA.`COLUMNS`").
		Select([]string{"COLUMN_NAME", "COLUMN_TYPE", "DATA_TYPE"}).
		Where("TABLE_NAME=? AND TABLE_SCHEMA=?", table, dbname).
		Order("ORDINAL_POSITION ASC").
		Find(&columns).Error
	HandleError(err)

	modelName := name

	haveTimeImport := false
	for _, item := range columns {
		item.DataType = strings.ToLower(item.DataType)
		item.DataType = DbTypeFormat(item.DataType)

		if item.DataType == "datetime" || item.DataType == "timestamp" {
			haveTimeImport = true
		}
	}

	plugin := "model"
	currentDir := GetCurrentDir()
	appDir := filepath.Clean(fmt.Sprintf("%s/app/%s", currentDir, app))
	if exits, err := PathExists(appDir); !exits || err != nil {
		HandleError(appDir + "目录不存在")
	}
	pluginDir := filepath.Clean(fmt.Sprintf("%s/app/%s/%s", currentDir, app, plugin))
	if exits, err := PathExists(pluginDir); !exits || err != nil {
		HandleError("app 不存在")
	}
	targetFilePath := filepath.Clean(fmt.Sprintf("%s/app/%s/%s/%s.go", currentDir, app, plugin, modelName))

	if cHelper.IsExistsPath(targetFilePath) {
		contentByte, _ := os.ReadFile(targetFilePath)
		content := string(contentByte)

		var structDefined string
		structDefineds := FindStructFromContent(content)
		for _, defined := range structDefineds {
			if strings.Contains(defined, fmt.Sprintf("type %s struct", name)) {
				structDefined = defined
			}
		}

		if structDefined == "" {
			log.Panicf("model 文件未发现 %s struct: %s", name, targetFilePath)
			return
		}

		structure := fmt.Sprintf("type %s struct {\n\tcDB.Model[*%s]\n", modelName, modelName)
		for _, item := range columns {
			itemName := cHelper.ToCamel(item.ColumnName)
			if strings.ToLower(item.ColumnName) == "id" {
				itemName = "ID"
			}
			itemType := DbTypeToGoType(item.DataType, item.ColumnType)
			if item.ColumnName == "delete_time" {
				itemType = "soft_delete.DeletedAt"
				content = strings.ReplaceAll(content, "\"github.com/gin-gonic/gin\"", "\"github.com/gin-gonic/gin\"\n\t\"gorm.io/plugin/soft_delete\"")
			}

			var serializeStr string
			if item.ColumnType == "json" {
				serializeStr = ";serializer:json"

				fieldMatches := regexp.MustCompile(fmt.Sprintf(" *%s +([^ ]+) +`gorm", itemName)).FindAllStringSubmatch(structDefined, -1)
				if len(fieldMatches) > 0 {
					itemType = fieldMatches[0][1]
				}
			}

			var autoTimeStr string
			if item.ColumnName == "create_time" {
				autoTimeStr = ";autoCreateTime"
			}
			if item.ColumnName == "update_time" {
				autoTimeStr = ";autoUpdateTime"
			}

			structure = fmt.Sprintf("%s\t%s %s `gorm:\"column:%s%s%s\" json:\"%s\"`\n", structure, itemName, itemType, item.ColumnName, serializeStr, autoTimeStr, item.ColumnName)
		}
		structure = fmt.Sprintf("%s\n}", structure)

		content = strings.ReplaceAll(content, structDefined, structure)

		err = os.WriteFile(targetFilePath, []byte(content), 0755)
		HandleError(err)
	} else {
		importContent := "\n\nimport (\n\t\"gitee.com/csingo/cDB\"\n\t\"github.com/gin-gonic/gin\"\n)"
		if haveTimeImport {
			importContent = strings.ReplaceAll(importContent, ")", "\t\"time\"\n)")
		}

		content := fmt.Sprintf("package model%s\n\ntype %s struct {\n\tcDB.Model[*%s]\n", importContent, modelName, modelName)
		for _, item := range columns {
			itemName := cHelper.ToCamel(item.ColumnName)
			if strings.ToLower(item.ColumnName) == "id" {
				itemName = "ID"
			}
			itemType := DbTypeToGoType(item.DataType, item.ColumnType)
			if item.ColumnName == "delete_time" {
				itemType = "soft_delete.DeletedAt"
				content = strings.ReplaceAll(content, "\"github.com/gin-gonic/gin\"", "\"github.com/gin-gonic/gin\"\n\t\"gorm.io/plugin/soft_delete\"")
			}

			var serializeStr string
			if item.ColumnType == "json" {
				serializeStr = ";serializer:json"
			}

			var autoTimeStr string
			if item.ColumnName == "create_time" {
				autoTimeStr = ";autoCreateTime"
			}
			if item.ColumnName == "update_time" {
				autoTimeStr = ";autoUpdateTime"
			}

			content = fmt.Sprintf("%s\t%s %s `gorm:\"column:%s%s%s\" json:\"%s\"`\n", content, itemName, itemType, item.ColumnName, serializeStr, autoTimeStr, item.ColumnName)
		}
		content = fmt.Sprintf("%s}\n\nfunc (i *%s) Connection(ctx *gin.Context) string {\n\treturn \"%s\"\n}", content, modelName, driver)
		if table != modelName {
			content = fmt.Sprintf("%s\n\nfunc (i *%s) TableName() string {\n\treturn \"%s\"\n}", content, modelName, table)
		}

		demoContent := i.contentFromDemo(app, plugin, "Model")
		demoAllContents := strings.Split(demoContent, "\n// ==========文件切割符，请勿编辑==========")
		methods := demoAllContents[1]

		methods = strings.ReplaceAll(methods, "HelloModel", name)
		content = content + "\n" + methods

		err = os.WriteFile(targetFilePath, []byte(content), 0755)
		HandleError(err)
	}

	projectDir := GetCurrentDir()
	Exec(fmt.Sprintf("go fmt %s", targetFilePath), projectDir)

	i.autoloadInject(app, name, "model")
	BeautifyImports()
}

func (i *AppCommand) Domain(app, name string) {
	i.createFileFromDemo(app, name, "domain", nil)
	i.autoloadInject(app, name, "domain")
	BeautifyImports()
}
func (i *AppCommand) Event(app, name string) {
	i.createFileFromDemo(app, name, "event", nil)
	i.autoloadInject(app, name, "event")
	BeautifyImports()
}

func (i *AppCommand) Service(app, grpc, protoPath string) {
	ParamCheck("app", app)
	// ParamCheck("protoPath", protoPath)
	projectDir := GetCurrentDir()
	if protoPath == "" {
		protoPath = filepath.Clean(fmt.Sprintf("%s/proto", projectDir))
	}
	i.createPbFile(app, app, grpc, protoPath)

	i.useGrpc(protoPath, app, grpc)
	BeautifyImports()
}

func (i *AppCommand) Sdk(app, pkg, grpc, protoPath string) {
	ParamCheck("app", app)

	if pkg == "" {
		pkg = app
	}

	projectDir := GetCurrentDir()
	if protoPath == "" {
		protoPath = filepath.Clean(fmt.Sprintf("%s/proto", projectDir))
	}
	i.createPbFile(app, pkg, grpc, protoPath)
	BeautifyImports()
}

func (i *AppCommand) contentFromDemo(app, plugin, title string) (content string) {
	currentDir := GetCurrentDir()
	appDir := filepath.Clean(fmt.Sprintf("%s/app/%s", currentDir, app))
	if exits, err := PathExists(appDir); !exits || err != nil {
		HandleError(appDir + "目录不存在")
	}
	pluginDir := filepath.Clean(fmt.Sprintf("%s/app/%s/%s", currentDir, app, plugin))
	if exits, err := PathExists(pluginDir); !exits || err != nil {
		HandleError("app 不存在")
	}

	var err error
	var contentBytes []byte
	contentBytes, err = template.Asset(fmt.Sprintf("template/app/demo/%s/Hello%s.go.tpl", plugin, title))
	HandleError(err)

	content = string(contentBytes)
	return
}

func (i *AppCommand) createFileFromDemo(app, name, plugin string, replaceMapping map[string]string) {
	ParamCheck("app", app)
	ParamCheck("name", name)
	pluginTitle := strings.Title(plugin)
	currentDir := GetCurrentDir()

	// 替换内容
	str := i.contentFromDemo(app, plugin, pluginTitle)
	str = strings.Replace(str, "demo", app, -1)
	str = strings.Replace(str, fmt.Sprintf("Hello%s", pluginTitle), name, -1)
	if replaceMapping != nil {
		for k, v := range replaceMapping {
			if k == "demo" || k == "Hello" {
				continue
			}
			str = strings.Replace(str, fmt.Sprintf("%s", k), v, -1)
		}
	}
	targetFilePath := filepath.Clean(fmt.Sprintf("%s/app/%s/%s/%s.go", currentDir, app, plugin, name))

	// 写入文件内容
	exits, err := PathExists(targetFilePath)
	HandleError(err)
	if !exits {
		err = os.WriteFile(targetFilePath, []byte(str), 0755)
		HandleError(err)
	}
	projectDir := GetCurrentDir()
	Exec(fmt.Sprintf("go fmt %s", targetFilePath), projectDir)
}

func (i *AppCommand) autoloadInject(app, name, plugin string) {
	pluginTitle := strings.Title(plugin)
	currentDir := GetCurrentDir()
	project, err := ProjectName()
	HandleError(err)
	// 判断autoload文件是否存在
	autoloadFilePath := filepath.Clean(fmt.Sprintf("%s/autoload/%s.go", currentDir, pluginTitle))
	if exits, err := PathExists(autoloadFilePath); !exits || err != nil {
		HandleError("autoload file 不存在")
	}

	content, err := os.ReadFile(autoloadFilePath)
	HandleError(err)
	autoloadContent := string(content)

	// 添加autoload文件import内容
	targetImportContent := fmt.Sprintf("%s%ss \"%s/app/%s/%s\"", strings.Title(app), pluginTitle, project, app, plugin)
	if strings.Contains(autoloadContent, "import (") {
		if !strings.Contains(autoloadContent, targetImportContent) {
			autoloadContent = strings.Replace(autoloadContent, "import (", fmt.Sprintf("import (\n\t%s", targetImportContent), -1)
		}
	} else {
		autoloadContent = strings.Replace(autoloadContent, "package autoload", fmt.Sprintf("package autoload\n\nimport (\n\t\"gitee.com/csingo/cServer\"\n\t%s%ss \"%s/app/%s/%s\"\n)", strings.Title(app), pluginTitle, project, app, plugin), -1)
	}

	// 添加autoload文件inject内容
	targetInjectContent := fmt.Sprintf("cServer.Inject(&%s%ss.%s{})", strings.Title(app), pluginTitle, name)
	demoInjectContent := fmt.Sprintf("func Inject%s() {", pluginTitle)
	if !strings.Contains(autoloadContent, targetInjectContent) {
		autoloadContent = strings.Replace(autoloadContent, demoInjectContent, fmt.Sprintf("%s\n\t%s", demoInjectContent, targetInjectContent), -1)
	}

	err = os.WriteFile(autoloadFilePath, []byte(autoloadContent), 0755)
	HandleError(err)
}

type Constants struct {
	Name   string
	Descs  []*ConstantDesc
	Errors []*ConstantError
}

type ConstantDesc struct {
	Value int
	Desc  string
}

type ConstantError struct {
	Value int
	Error string
}

func (i *AppCommand) createPbFile(app, pkg, grpc, protoPath string) {
	// protoc --go_out=base/demo -I D:/project/csingo/cxy/proto demo/message.proto demo/hello_service.proto
	project, err := ProjectName()
	HandleError(err)
	pkg = strings.ReplaceAll(pkg, "-", "_")
	projectDir := GetCurrentDir()
	protoPath = filepath.Clean(protoPath)
	outPath := filepath.Clean(fmt.Sprintf("base/%s", app))
	pkgPath := filepath.Clean(fmt.Sprintf("base/%s", pkg))

	// 清理历史文件
	_ = os.RemoveAll(pkgPath)

	if app != pkg {
		outPath = filepath.Clean(fmt.Sprintf("base/tmp/%s", app))
		err = os.MkdirAll(filepath.Clean(fmt.Sprintf("%s/base/tmp/%s", projectDir, app)), 0755)
		HandleError(err)
	}

	pkgName := pkg
	//if strings.Contains(pkg, "-") || strings.Contains(pkg, "_") {
	//	pkgName = cHelper.ToCamelWithChars(pkg, []string{"-", "_"})
	//}
	command := []string{
		"protoc",
		fmt.Sprintf("--go_out=%s", outPath),
	}
	if grpc == "true" {
		command = append(command, fmt.Sprintf("--go-grpc_out=%s", outPath))
	}
	command = append(command, fmt.Sprintf("-I %s", protoPath))

	demoServerContentByte, err := template.Asset("template/base/demo/HelloServiceHttpServer.go.tpl")
	HandleError(err)
	demoServerContent := string(demoServerContentByte)
	demoClientContentByte, err := template.Asset("template/base/demo/HelloServiceHttpClient.go.tpl")
	HandleError(err)
	demoClientAllContent := string(demoClientContentByte)
	demoGrpcClientContentByte, err := template.Asset("template/base/demo/HelloServiceGrpcClient.go.tpl")
	HandleError(err)
	demoGrpcClientAllContent := string(demoGrpcClientContentByte)
	demoGitkeepContentByte, err := template.Asset("template/base/demo/.gitkeep.tpl")
	HandleError(err)

	// 切割client
	contentArr := strings.Split(demoClientAllContent, "\n// ==========文件切割符，请勿编辑==========")
	demoClientContent := contentArr[0]
	demoClientMethodContent := contentArr[1]

	// 切割client
	grpcContentArr := strings.Split(demoGrpcClientAllContent, "\n// ==========文件切割符，请勿编辑==========")
	demoGrpcClientContent := grpcContentArr[0]
	demoGrpcClientMethodContent := grpcContentArr[1]

	walkPath := filepath.Clean(fmt.Sprintf("%s/%s", protoPath, app))
	err = filepath.Walk(walkPath, func(pbfile string, info fs.FileInfo, err error) error {
		pbfile = filepath.Clean(pbfile)
		ext := path.Ext(pbfile)
		if ext == ".proto" {
			pbfileRelativePath := strings.TrimPrefix(pbfile, protoPath)
			pbfileRelativePath = strings.TrimPrefix(pbfileRelativePath, filepath.Clean("/"))
			command = append(command, pbfileRelativePath)
		}
		return err
	})
	HandleError(err)

	err = os.MkdirAll(filepath.Clean(fmt.Sprintf("%s/base/%s", projectDir, pkg)), 0755)
	HandleError(err)
	err = os.WriteFile(filepath.Clean(fmt.Sprintf("%s/base/%s/.gitkeep", projectDir, pkg)), demoGitkeepContentByte, 0755)
	HandleError(err)

	cmd := strings.Join(command, " ")
	cmd = strings.Replace(cmd, `\\`, "/", -1)
	cmd = strings.Replace(cmd, `\`, "/", -1)
	// 执行protoc
	Exec(cmd, projectDir)

	// 生成文件
	err = filepath.Walk(walkPath, func(pbfile string, info fs.FileInfo, err error) error {
		pbfile = filepath.Clean(pbfile)
		ext := path.Ext(pbfile)
		if ext != ".proto" {
			return err
		}

		rpcServices := cProto.ParseService(pbfile)
		if len(rpcServices) == 0 {
			return err
		}

		for _, rpcService := range rpcServices {
			// 生成GrpcServer.go
			if grpc == "true" {
				targetGrpcServerFilePath := filepath.Clean(fmt.Sprintf("%s/base/%s/%sGrpcServer.go", projectDir, pkg, rpcService.ServiceName))
				targetGrpcServerContent := fmt.Sprintf("package %s\n\nimport (\n\t\"context\"\n\t\"github.com/gin-gonic/gin\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\ntype %sGrpcServer struct{}", pkgName, rpcService.ServiceName)
				var methodDesc, methodHandler string
				interfaceContent := fmt.Sprintf("type %sGrpcServerInterface interface {", rpcService.ServiceName)
				for _, method := range rpcService.ServiceRpc {
					targetGrpcServerContent = fmt.Sprintf(
						"%s\n\nfunc (%sGrpcServer) %s(*gin.Context, *%s) (*%s, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method Hello not implemented\")\n}",
						targetGrpcServerContent,
						rpcService.ServiceName,
						method.RpcName,
						method.RpcRequest,
						method.RpcResponse,
					)
					interfaceContent = fmt.Sprintf(
						"%s\n\t%s(*gin.Context, *%s) (*%s, error)",
						interfaceContent,
						method.RpcName,
						method.RpcRequest,
						method.RpcResponse,
					)
					methodDesc = fmt.Sprintf(
						"%s\t\t\t{\n\t\t\t\tMethodName: \"%s\",\n\t\t\t\tHandler:    %s%sHandler,\n\t\t\t},",
						methodDesc,
						method.RpcName,
						rpcService.ServiceName,
						method.RpcName,
					)
					methodHandler = fmt.Sprintf(
						"%s\n\nfunc %s%sHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {\n\tin := new(%s)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(%sGrpcServer).%s(ctx.(*gin.Context), in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: %s_%s_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req any) (any, error) {\n\t\treturn srv.(%sGrpcServerInterface).%s(ctx.(*gin.Context), req.(*%s))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}",
						methodHandler,
						rpcService.ServiceName,
						method.RpcName,
						method.RpcRequest,
						rpcService.ServiceName,
						method.RpcName,
						rpcService.ServiceName,
						method.RpcName,
						rpcService.ServiceName,
						method.RpcName,
						method.RpcRequest,
					)
				}
				targetGrpcServerContent = fmt.Sprintf(
					"%s\n\nfunc (i %sGrpcServer) GrpcServiceDesc() *grpc.ServiceDesc {\n\treturn &grpc.ServiceDesc{\n\t\tServiceName: \"%s.%s\",\n\t\tHandlerType: (*%sGrpcServerInterface)(nil),\n\t\tMethods: []grpc.MethodDesc{\n%s\n\t\t},\n\t\tStreams:  []grpc.StreamDesc{},\n\t\tMetadata: \"%s/%s.proto\",\n\t}\n}%s\n\n%s\n}\n",
					targetGrpcServerContent,
					rpcService.ServiceName,
					pkgName,
					rpcService.ServiceName,
					rpcService.ServiceName,
					methodDesc,
					pkgName,
					rpcService.ServiceName,
					methodHandler,
					interfaceContent,
				)

				err = os.WriteFile(targetGrpcServerFilePath, []byte(targetGrpcServerContent), 0755)
				if err != nil {
					return err
				}
			}

			// 生成GrpcClient.go
			if grpc == "true" {
				targetGrpcClientFilePath := filepath.Clean(fmt.Sprintf("%s/base/%s/%sGrpcClient.go", projectDir, pkg, rpcService.ServiceName))
				targetGrpcClientContent := strings.ReplaceAll(demoGrpcClientContent, "package demo", fmt.Sprintf("package %s", pkgName))
				targetGrpcClientContent = strings.ReplaceAll(targetGrpcClientContent, "self", pkgName)
				targetGrpcClientContent = strings.ReplaceAll(targetGrpcClientContent, "demo", app)
				targetGrpcClientContent = strings.ReplaceAll(targetGrpcClientContent, "HelloService", rpcService.ServiceName)

				// client.go 拼装方法
				for _, method := range rpcService.ServiceRpc {
					targetGrpcClientMethodContent := strings.ReplaceAll(demoGrpcClientMethodContent, "HelloServiceHelloReq", method.RpcRequest)
					targetGrpcClientMethodContent = strings.ReplaceAll(targetGrpcClientMethodContent, "HelloServiceHelloRsp", method.RpcResponse)
					targetGrpcClientMethodContent = strings.ReplaceAll(targetGrpcClientMethodContent, "HelloService", rpcService.ServiceName)
					targetGrpcClientMethodContent = strings.ReplaceAll(targetGrpcClientMethodContent, "Hello", method.RpcName)
					targetGrpcClientContent = fmt.Sprintf("%s%s", targetGrpcClientContent, targetGrpcClientMethodContent)
				}

				err = os.WriteFile(targetGrpcClientFilePath, []byte(targetGrpcClientContent), 0755)
				if err != nil {
					return err
				}
			}

			// 生成server.go
			targetServerFilePath := filepath.Clean(fmt.Sprintf("%s/base/%s/%sHttpServer.go", projectDir, pkg, rpcService.ServiceName))
			targetServerContent := strings.ReplaceAll(demoServerContent, "package demo", fmt.Sprintf("package %s", pkgName))
			targetServerContent = strings.ReplaceAll(targetServerContent, "demo", app)
			targetServerContent = strings.ReplaceAll(targetServerContent, "HelloService", rpcService.ServiceName)
			err = os.WriteFile(targetServerFilePath, []byte(targetServerContent), 0755)
			if err != nil {
				return err
			}

			// 生成client.go
			targetClientFilePath := filepath.Clean(fmt.Sprintf("%s/base/%s/%sHttpClient.go", projectDir, pkg, rpcService.ServiceName))
			targetClientContent := strings.ReplaceAll(demoClientContent, "package demo", fmt.Sprintf("package %s", pkgName))
			targetClientContent = strings.ReplaceAll(targetClientContent, "self", pkgName)
			targetClientContent = strings.ReplaceAll(targetClientContent, "demo", app)
			targetClientContent = strings.ReplaceAll(targetClientContent, "HelloService", rpcService.ServiceName)

			// client.go 拼装方法
			for _, method := range rpcService.ServiceRpc {
				targetClientMethodContent := strings.ReplaceAll(demoClientMethodContent, "HelloServiceHelloReq", method.RpcRequest)
				targetClientMethodContent = strings.ReplaceAll(targetClientMethodContent, "HelloServiceHelloRsp", method.RpcResponse)
				targetClientMethodContent = strings.ReplaceAll(targetClientMethodContent, "HelloService", rpcService.ServiceName)
				targetClientMethodContent = strings.ReplaceAll(targetClientMethodContent, "Hello", method.RpcName)
				targetClientContent = fmt.Sprintf("%s%s", targetClientContent, targetClientMethodContent)
			}

			err = os.WriteFile(targetClientFilePath, []byte(targetClientContent), 0755)
			if err != nil {
				return err
			}
		}

		return err
	})
	HandleError(err)

	// 修改pb.go
	walkPath = filepath.Clean(fmt.Sprintf("%s/base/%s", projectDir, app))
	if app != pkg {
		walkPath = filepath.Clean(fmt.Sprintf("%s/base/tmp/%s", projectDir, app))
	}
	err = filepath.Walk(walkPath, func(pbfile string, info fs.FileInfo, err error) error {
		pbfile = filepath.Clean(pbfile)
		if !regexp.MustCompile(`\.pb\.go$`).MatchString(pbfile) {
			return err
		}

		// 修改omitempty
		contentByte, err := os.ReadFile(pbfile)
		if err != nil {
			return err
		}
		content := string(contentByte)
		content = regexp.MustCompile(`.*,omitempty.*<omitempty>`).ReplaceAllStringFunc(content, func(s string) string {
			s = strings.ReplaceAll(s, "omitempty", "##OMITEMPTY##")
			return s
		})
		content = strings.ReplaceAll(content, ",omitempty", "")
		content = strings.ReplaceAll(content, "##OMITEMPTY##", "omitempty")

		// 修改 package
		content = strings.ReplaceAll(content, fmt.Sprintf("package %s", app), fmt.Sprintf("package %s", pkgName))

		// 修改import
		importContents := FindImport(content)
		for _, importContent := range importContents {
			if strings.Contains(importContent, "../") {
				replaceImportContent := strings.ReplaceAll(importContent, "../", fmt.Sprintf("%s/base/", project))
				content = strings.ReplaceAll(content, importContent, replaceImportContent)
			}
		}

		// 增加枚举值desc方法
		constants := FindConstant(content)
		constantsMapping := make(map[string]*Constants)
		for _, constant := range constants {
			items := regexp.MustCompile(` *([\w_]+) +([\w_\[\]\{\}]+) *= *([\d]+) *\/\/.*<desc:([^>]*)>.*`).FindAllStringSubmatch(constant, -1)
			for _, item := range items {
				if _, ok := constantsMapping[item[2]]; !ok {
					constantsMapping[item[2]] = &Constants{
						Name:  item[2],
						Descs: []*ConstantDesc{},
					}
				}
				val, _ := strconv.Atoi(item[3])
				constantsMapping[item[2]].Descs = append(constantsMapping[item[2]].Descs, &ConstantDesc{
					Value: val,
					Desc:  item[4],
				})
			}
		}
		funcTargetContent := ""
		constantTargetContent := "var (\n"
		for _, constItem := range constantsMapping {
			constantTargetContent = fmt.Sprintf("%s\t%s_desc = map[int32]string{\n", constantTargetContent, constItem.Name)
			for _, desc := range constItem.Descs {
				constantTargetContent = fmt.Sprintf("%s\t\t%d:\t\"%s\",\n", constantTargetContent, desc.Value, desc.Desc)
			}
			constantTargetContent = fmt.Sprintf("%s\t}\n", constantTargetContent)
			funcTargetContent = fmt.Sprintf("%s\nfunc (x %s) Desc() string {\n\tif _, ok := %s_desc[int32(x)]; !ok {\n\t\treturn \"\"\n\t}\n\treturn %s_desc[int32(x)]\n}\n", funcTargetContent, constItem.Name, constItem.Name, constItem.Name)
		}
		constantTargetContent = fmt.Sprintf("%s)\n", constantTargetContent)
		content = fmt.Sprintf("%s\n%s\n%s", content, constantTargetContent, funcTargetContent)

		// 增加枚举值error方法
		constantsErrorMapping := make(map[string]*Constants)
		for _, constant := range constants {
			items := regexp.MustCompile(` *([\w_]+) +([\w_\[\]\{\}]+) *= *([\d]+) *\/\/.*<error:([^>]*)>.*`).FindAllStringSubmatch(constant, -1)
			for _, item := range items {
				if _, ok := constantsErrorMapping[item[2]]; !ok {
					constantsErrorMapping[item[2]] = &Constants{
						Name:   item[2],
						Errors: []*ConstantError{},
					}
				}
				val, _ := strconv.Atoi(item[3])
				constantsErrorMapping[item[2]].Errors = append(constantsErrorMapping[item[2]].Errors, &ConstantError{
					Value: val,
					Error: item[4],
				})
			}
		}
		funcErrorTargetContent := ""
		constantErrorTargetContent := "var (\n"
		for _, constItem := range constantsErrorMapping {
			constantErrorTargetContent = fmt.Sprintf("%s\t%s_error = map[int32]string{\n", constantErrorTargetContent, constItem.Name)
			for _, errDesc := range constItem.Errors {
				constantErrorTargetContent = fmt.Sprintf("%s\t\t%d:\t\"%s\",\n", constantErrorTargetContent, errDesc.Value, errDesc.Error)
			}
			constantErrorTargetContent = fmt.Sprintf("%s\t}\n", constantErrorTargetContent)
			funcErrorTargetContent = fmt.Sprintf("%s\nfunc (x %s) Error() string {\n\tif _, ok := %s_error[int32(x)]; !ok {\n\t\treturn \"\"\n\t}\n\treturn %s_error[int32(x)]\n}\n", funcErrorTargetContent, constItem.Name, constItem.Name, constItem.Name)
			funcErrorTargetContent = fmt.Sprintf("%s\nfunc (x %s) Code() int32 {\n\treturn int32(x)\n}\n", funcErrorTargetContent, constItem.Name)
		}
		constantErrorTargetContent = fmt.Sprintf("%s)\n", constantErrorTargetContent)
		content = fmt.Sprintf("%s\n%s\n%s", content, constantErrorTargetContent, funcErrorTargetContent)

		// 修改struct属性类型
		structures := FindStructFromContent(content)
		for _, structure := range structures {
			structureByte := []byte(structure)
			structureMatches := regexp.MustCompile(`type ([\w\d_]+) struct`).FindAllStringSubmatch(structure, -1)
			structureAttrs := regexp.MustCompile(`([\w_]+) +([\w_\[\]\{\}]+) +.*<type:([^>]*)>.*`).FindAllStringSubmatch(structure, -1)
			structureAttrIndies := regexp.MustCompile(`([\w_]+) +([\w_\[\]\{\}]+) +.*<type:([^>]*)>.*`).FindAllStringSubmatchIndex(structure, -1)
			structureMame := structureMatches[0][1]
			for k, index := range structureAttrIndies {
				oldAttrContent := structureAttrs[k][0]
				attrName := structureAttrs[k][1]
				oldType := structureAttrs[k][2]
				newType := structureAttrs[k][3]
				newAttrContent := fmt.Sprintf("%s %s %s", string(structureByte[index[0]:index[4]-1]), newType, string(structureByte[index[5]+1:index[1]]))
				content = strings.ReplaceAll(content, oldAttrContent, newAttrContent)

				oldTypeDefaultVal := GetDefaultValueToString(oldType)
				newTypeDefaultVal := GetDefaultValueToString(newType)
				oldAttrFunc := fmt.Sprintf("func (x *%s) Get%s() %s {\n\tif x != nil {\n\t\treturn x.%s\n\t}\n\treturn %s\n}", structureMame, attrName, oldType, attrName, oldTypeDefaultVal)
				newAttrFunc := fmt.Sprintf("func (x *%s) Get%s() %s {\n\tif x != nil {\n\t\treturn x.%s\n\t}\n\treturn %s\n}", structureMame, attrName, newType, attrName, newTypeDefaultVal)
				content = strings.ReplaceAll(content, oldAttrFunc, newAttrFunc)
			}
		}

		// 修改继承属性
		structures = FindStructFromContent(content)
		for _, structure := range structures {
			structureMatches := regexp.MustCompile(`type ([\w\d_]+) struct`).FindAllStringSubmatch(structure, -1)
			structureName := structureMatches[0][1]
			newStructure := regexp.MustCompile(`( *)(\w+)( +)([\w\*]+)( +)(.*//.*<extend>)`).ReplaceAllString(structure, "$1$4 // <extend>")
			attrMatches := regexp.MustCompile(`( *)(\w+)( +)([\w\*]+)( +)(.*//.*<extend>)`).FindAllStringSubmatch(structure, -1)
			if len(attrMatches) > 0 {
				attrFunc := fmt.Sprintf(
					"func (x *%s) Get%s() %s {\n\tif x != nil {\n\t\treturn x.%s\n\t}\n\treturn nil\n}",
					structureName,
					attrMatches[0][2],
					attrMatches[0][4],
					attrMatches[0][2],
				)
				content = strings.ReplaceAll(content, attrFunc+"\n", "")
			}
			content = strings.ReplaceAll(content, structure, newStructure)
		}

		// 修改json解析
		jsonStructures := FindStructFromContent(content)
		for _, structure := range jsonStructures {
			structureJsons := regexp.MustCompile(`([\w_]+) +(\**[\w_\[\]\{\}]+) +.*json:"([^"]*)".*<json:([^>]*)>.*`).FindAllStringSubmatch(structure, -1)
			for _, jsonMatch := range structureJsons {
				oldJsonContent := jsonMatch[0]
				newJsonContent := strings.ReplaceAll(oldJsonContent, fmt.Sprintf("json:\"%s\"", jsonMatch[3]), fmt.Sprintf("json:\"%s\"", jsonMatch[4]))
				content = strings.ReplaceAll(content, oldJsonContent, newJsonContent)
			}
		}

		// 增加form参数
		content = regexp.MustCompile(`json:"([^"]*)"`).ReplaceAllString(content, "$0 form:\"$1\" xml:\"$1\" uri:\"$1\"")

		// 增加binding参数
		content = regexp.MustCompile("` *//.*<validator:([^>]*)>").ReplaceAllString(content, " binding:\"$1\"$0")

		// 删除空var
		content = regexp.MustCompile(`var *\(\n*\)`).ReplaceAllString(content, "")

		// 删除多余结尾
		content = regexp.MustCompile(`\n\n$`).ReplaceAllString(content, "")

		writePath := filepath.Clean(fmt.Sprintf("%s/%s", pkgPath, info.Name()))
		err = os.WriteFile(writePath, []byte(content), 0755)
		if err != nil {
			return err
		}

		// 格式化文件
		Exec(fmt.Sprintf("go fmt %s", pbfile), projectDir)

		return err
	})
	HandleError(err)

	err = os.RemoveAll(filepath.Clean("base/tmp"))
	HandleError(err)

	i.createRpcClientInject()
}

func (i *AppCommand) createRpcClientInject() {
	project, err := ProjectName()
	HandleError(err)

	projectDir := GetCurrentDir()
	basePath := filepath.Clean(fmt.Sprintf("%s/base", projectDir))
	outPath := filepath.Clean(fmt.Sprintf("%s/autoload/RpcClient.go", projectDir))

	imports := []string{}
	clients := []string{}

	reg := `func +\(\w+ +\*?(\w+)\) +RpcClientName`
	err = filepath.Walk(basePath, func(fPath string, info fs.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}
		contentBytes, err := os.ReadFile(fPath)
		if err != nil {
			return err
		}
		matches := regexp.MustCompile(reg).FindAllStringSubmatch(string(contentBytes), -1)
		if len(matches) != 1 || len(matches[0]) != 2 {
			return nil
		}

		relativePath := strings.TrimPrefix(fPath, basePath)
		relativePath = strings.TrimLeft(relativePath, "/")
		relativePath = strings.TrimLeft(relativePath, "\\")
		pkgName := filepath.Dir(relativePath)

		importItem := fmt.Sprintf("%s/base/%s", project, pkgName)
		clientItem := fmt.Sprintf("cServer.Inject(&%s.%s{})", pkgName, matches[0][1])
		if !cHelper.InArray(imports, importItem) {
			imports = append(imports, importItem)
		}
		if !cHelper.InArray(clients, clientItem) {
			clients = append(clients, clientItem)
		}

		return err
	})
	HandleError(err)

	var outContent string
	if len(clients) == 0 {
		outContent = "package autoload\n\nfunc InjectRpcClient() {\n}\n"
	} else {
		importData := "import (\n\t\"gitee.com/csingo/cServer\""
		for _, item := range imports {
			importData = fmt.Sprintf("%s\n\t\"%s\"", importData, item)
		}
		importData = fmt.Sprintf("%s\n)", importData)

		funcData := "func InjectRpcClient() {"
		for _, item := range clients {
			funcData = fmt.Sprintf("%s\n\t%s", funcData, item)
		}
		funcData = fmt.Sprintf("%s\n}", funcData)

		outContent = fmt.Sprintf("package autoload\n\n%s\n\n%s\n", importData, funcData)
	}

	err = os.WriteFile(outPath, []byte(outContent), 0755)
	HandleError(err)
}

func (i *AppCommand) useGrpc(protoPath, app, grpc string) {
	projectDir := GetCurrentDir()
	project, err := ProjectName()
	HandleError(err)
	protoPath = filepath.Clean(protoPath)
	demoServiceContentByte, err := template.Asset("template/app/demo/service/HelloService.go.tpl")
	HandleError(err)
	demoServiceAllContent := strings.Split(string(demoServiceContentByte), "\n// ==========文件切割符，请勿编辑==========")
	demoServiceStructContent := demoServiceAllContent[0]
	demoServiceMethodContent := demoServiceAllContent[1]

	// 不启用grpc
	demoServiceStructContent = strings.ReplaceAll(demoServiceStructContent, "\n\tdemo.HelloServiceGrpcServer", "")

	walkPath := filepath.Clean(fmt.Sprintf("%s/%s", protoPath, app))
	err = filepath.Walk(walkPath, func(pbfile string, info fs.FileInfo, err error) error {
		pbfile = filepath.Clean(pbfile)
		ext := path.Ext(pbfile)
		if ext != ".proto" {
			return err
		}

		rpcServices := cProto.ParseService(pbfile)
		if len(rpcServices) == 0 {
			return err
		}

		for _, service := range rpcServices {
			targetServicePath := filepath.Clean(fmt.Sprintf("%s/app/%s/service/%s.go", projectDir, app, service.ServiceName))
			// 判断文件是否存在
			exists, err := PathExists(targetServicePath)
			if err != nil {
				return err
			}
			var targetServiceContent string
			if !exists {
				targetServiceContent = demoServiceStructContent
				targetServiceContent = strings.ReplaceAll(targetServiceContent, "gitee.com/csingo/example", project)
				targetServiceContent = strings.ReplaceAll(targetServiceContent, "demo", app)
				targetServiceContent = strings.ReplaceAll(targetServiceContent, "HelloService", service.ServiceName)
			} else {
				targetServiceContentByte, err := os.ReadFile(targetServicePath)
				if err != nil {
					return err
				}
				targetServiceContent = string(targetServiceContentByte)
				targetServiceContent = strings.ReplaceAll(targetServiceContent, "gitee.com/csingo/example", project)
				// 不存在struct则添加
				structReg := regexp.MustCompile(fmt.Sprintf("type +%s +struct *{", service.ServiceName))
				if !structReg.MatchString(targetServiceContent) {
					targetServiceContent = targetServiceContent + fmt.Sprintf("\ntype %s struct {\n\t%s.%sHTTPServer\n}\n", service.ServiceName, app, service.ServiceName)
				}
				// 添加grpc
				if grpc == "true" && !strings.Contains(targetServiceContent, fmt.Sprintf("%s.%sGrpcServer", app, service.ServiceName)) {
					targetServiceContent = strings.ReplaceAll(
						targetServiceContent,
						fmt.Sprintf("%s.%sHTTPServer", app, service.ServiceName),
						fmt.Sprintf("%s.%sHTTPServer\n\t%s.%sGrpcServer", app, service.ServiceName, app, service.ServiceName),
					)
				}
				// 不存在import则添加
				importReg := regexp.MustCompile(fmt.Sprintf("\"%s/base/%s\"", project, app))
				if !importReg.MatchString(targetServiceContent) {
					AddImport(targetServiceContent, fmt.Sprintf("%s/demo/%s", project, app))
				}
			}

			// 处理rpc方法
			for _, method := range service.ServiceRpc {
				if !regexp.MustCompile(fmt.Sprintf(`func *\(\w +\*?%s *\) *%s\(`, service.ServiceName, method.RpcName)).MatchString(targetServiceContent) {
					methodContent := strings.ReplaceAll(demoServiceMethodContent, "HelloServiceHelloReq", method.RpcRequest)
					methodContent = strings.ReplaceAll(methodContent, "HelloServiceHelloRsp", method.RpcResponse)
					methodContent = strings.ReplaceAll(methodContent, "HelloService", service.ServiceName)
					methodContent = strings.ReplaceAll(methodContent, "Hello", method.RpcName)
					methodContent = strings.ReplaceAll(methodContent, "demo", app)
					targetServiceContent = targetServiceContent + methodContent
				}
			}
			err = os.WriteFile(targetServicePath, []byte(targetServiceContent), 0755)
			if err != nil {
				return err
			}
			i.autoloadInject(app, service.ServiceName, "service")
		}

		return err
	})
	HandleError(err)
}
